// ======================================================================== //
// Copyright 2018-2019 Ingo Wald                                            //
//                                                                          //
// Licensed under the Apache License, Version 2.0 (the "License");          //
// you may not use this file except in compliance with the License.         //
// You may obtain a copy of the License at                                  //
//                                                                          //
//     http://www.apache.org/licenses/LICENSE-2.0                           //
//                                                                          //
// Unless required by applicable law or agreed to in writing, software      //
// distributed under the License is distributed on an "AS IS" BASIS,        //
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
// See the License for the specific language governing permissions and      //
// limitations under the License.                                           //
// ======================================================================== //
#pragma once
#ifndef OBR_OPTIXUTIL_H
#define OBR_OPTIXUTIL_H

// optix 7
#include <cuda_runtime.h>
#include <iostream>
#include <optix.h>
#include <optix_stubs.h>
#include <sstream>
#include <stdexcept>

#define CUDA_CHECK(call)                                                                           \
    {                                                                                              \
        cudaError_t rc = ##call;                                                                   \
        if (rc != cudaSuccess)                                                                     \
        {                                                                                          \
            cudaError_t err = rc; /*cudaGetLastError();*/                                          \
            std::cerr << "CUDA Error " << cudaGetErrorName(err) << " (" << cudaGetErrorString(err) \
                      << ")" << std::endl;                                                         \
            throw std::runtime_error("");                                                          \
        }                                                                                          \
    }

#define CUDA_CHECK_NOEXCEPT(call)                                                                  \
    {                                                                                              \
        cuda##call;                                                                                \
    }

#define OPTIX_CHECK(call)                                                                          \
    {                                                                                              \
        OptixResult res = call;                                                                    \
        if (res != OPTIX_SUCCESS)                                                                  \
        {                                                                                          \
            fprintf(stderr, "Optix call (%s) failed with code %d (line %d)\n", #call, res,         \
                    __LINE__);                                                                     \
            fprintf(stderr, "error name: %s\n", optixGetErrorName(res));                           \
            fprintf(stderr, "error string: %s\n", optixGetErrorString(res));                       \
            throw std::runtime_error("");                                                          \
        }                                                                                          \
    }

#define OPTIX_CHECK_NOEXCEPT(call)                                                                 \
    {                                                                                              \
        OptixResult res = call;                                                                    \
        if (res != OPTIX_SUCCESS)                                                                  \
        {                                                                                          \
            fprintf(stderr, "Optix call (%s) failed with code %d (line %d)\n", #call, res,         \
                    __LINE__);                                                                     \
            fprintf(stderr, "error name: %s\n", optixGetErrorName(res));                           \
            fprintf(stderr, "error string: %s\n", optixGetErrorString(res));                       \
        }                                                                                          \
    }

#define CUDA_SYNC_CHECK()                                                                          \
    {                                                                                              \
        cudaDeviceSynchronize();                                                                   \
        cudaError_t error = cudaGetLastError();                                                    \
        if (error != cudaSuccess)                                                                  \
        {                                                                                          \
            fprintf(stderr, "error (%s: line %d): %s\n", __FILE__, __LINE__,                       \
                    cudaGetErrorString(error));                                                    \
        }                                                                                          \
    }

namespace CUDAUtilities
{
inline void printDeviceProp(int device)
{
    cudaDeviceProp prop;
    cudaGetDeviceProperties(&prop, device);
    printf("Device Name : %s.\n", prop.name);
    printf("totalGlobalMem : %zu MB.\n", prop.totalGlobalMem / (1024 * 1024));
    printf("sharedMemPerBlock : %zd.\n", prop.sharedMemPerBlock);
    printf("regsPerBlock : %d.\n", prop.regsPerBlock);
    printf("warpSize : %d.\n", prop.warpSize);
    printf("memPitch : %zd.\n", prop.memPitch);
    printf("maxThreadsPerBlock : %d.\n", prop.maxThreadsPerBlock);
    printf("maxThreadsDim[0 - 2] : %d %d %d.\n", prop.maxThreadsDim[0], prop.maxThreadsDim[1],
           prop.maxThreadsDim[2]);
    printf("maxGridSize[0 - 2] : %d %d %d.\n", prop.maxGridSize[0], prop.maxGridSize[1],
           prop.maxGridSize[2]);
    printf("totalConstMem : %zd.\n", prop.totalConstMem);
    printf("major.minor : %d.%d.\n", prop.major, prop.minor);
    printf("clockRate : %.3f GHz.\n", prop.clockRate / 1e6);
    printf("textureAlignment : %zd.\n", prop.textureAlignment);
    printf("deviceOverlap : %d.\n", prop.deviceOverlap);
    printf("multiProcessorCount : %d.\n", prop.multiProcessorCount);
    size_t heapsize;
    cudaThreadGetLimit(&heapsize, cudaLimitMallocHeapSize);
    printf("original heap size: %zd\n", heapsize);
}
} // namespace CUDAUtilities

#endif